Software environments that support multithreaded applications, for example, JAVA and the European Computer Manufacturers Association (ECMA) Common Language Infrastructure (CLI), typically include a synchronization mechanism for coordinating when one or more threads may access an object. As will be appreciated by those of ordinary skill in the art, a thread refers to a series of processor instructions organized into a single control flow of execution for processing one or more objects. An object is an instance of a class, where a class is a collection of data and methods to operate on such data. In the case of multiple threads of execution, care must be taken to prevent the multiple threads from modifying the same object simultaneously in a way that might place the object in an erroneous state. In particular, a thread may have critical sections that operate on objects that could be accessed simultaneously by another thread. Thus, multithreaded systems typically provide specialized statements to protect the operation of a thread's critical section from being corrupted by one or more other threads accessing such a shared object during critical section execution.
For example, JAVA source code may include a synchronized statement to protect objects from being accessed simultaneously by different threads. Use of the synchronized statement enables acquisition of an exclusive lock of an object identified by the synchronized statement. Thus, a thread may be prevented from executing a critical section of code until it can obtain an exclusive lock on a particular object identified by a synchronized statement. Moreover, once such a lock is obtained, no other thread can access the locked object, thereby preventing inadvertent corruption of the processing being performed during execution of a critical section of code. Such a locking procedure may be used to ensure that multiple threads cannot access shared objects in a manner that could cause conflicting execution of critical sections of code at the same time. Of course, application of the synchronized statement is generally used in cases where a particular program creates multiple threads to share objects and/or methods. If only one thread ever accesses a particular object and/or method, there is no need to protect it with a synchronized statement.
A synchronized statement in JAVA source code is normally converted to JAVA virtual machine (JVM) instructions, because, as is known in the art, JAVA source code is first compiled into bytecodes (i.e., JVM language) prior to being executed by the JVM. For example, a synchronized statement may be converted to a monitorenter JVM instruction to gain/acquire an exclusive lock on an object. As a compliment to the monitorenter instruction, a monitorexit JVM instruction is provided to unlock/release the exclusive lock on the object. Accordingly, if a thread successfully executes the monitorenter instruction upon an object, that thread gains temporary exclusive lock ownership of the object (i.e., it has gained a lock on the object to prevent other threads from accessing the critical sections of code). If another thread, or second thread, attempts to execute the monitorenter instruction upon the same object while the first thread has temporary exclusive ownership of the object, the second thread must wait (e.g., sleep or spin) until the first thread (i.e., the current lock owner) executes the monitorexit instruction to release its exclusive lock of the object.
Two state variables are typically used to describe the lock state of an object. The first state variable is a lock owner that corresponds to the thread identifier of the thread that currently owns the lock. The lock owner may be set to a NULL value or a NULL thread for the case in which the lock is not owned by any thread. The second state variable is a lock recursion counter that may be used to indicate the number of times that the lock owner has acquired the lock (to support recursive locking). Typically, the lock state of an object is initialized to have a lock owner equal to a NULL value (corresponding to an unlocked state) and a lock recursion counter equal to zero.
Typically, a lockword is used to represent the lock state of an object. A common technique for object locking/synchronization is defined by Bacon, et al. in, ˜Thin Lock: Featherweight Synchronization for JAVA,” Conference on Programming Languages Design and Implementation, 1998, pp. 258-268. Bacon, et al. define a technique based on a lockword that may have two shapes, namely, a thin shape and an inflated shape. A thin lock may include only the thread identifier and lock recursion counter state variables and, due to its relatively small size, may be stored inside a header of the object. An inflated lock may include a number of additional data structures to support object locking/synchronization for scenarios in which a thin lock may not be sufficient (e.g., in the case of lock contention during which a list of threads waiting to acquire the object lock may be needed). Typically, an object is initialized to have a thin lock. The locking/synchronization procedure then inflates the lock, for example, when it becomes too large to fit in the object header (e.g., if there is lock contention and a list of waiting threads is required, if the lock recursion counter exceeds the maximum value that may be represented by the thin lock, etc.). In most implementations, the locking/synchronization procedure does not deflate an inflated lock back to a thin lock (i.e., once the object lock is converted to an inflated lock it remains an inflated lock for the remainder of program execution).
In many prior-art object locking techniques (e.g., prior art implementations of the JVM monitorenter and monitorexit instructions), the lock release function (e.g., corresponding to monitorexit instruction) determines whether a thread attempting to release the lock is actually the lock owner of the lock. Additionally, the lock release function checks the lock recursion counter to determine whether the lock should be unlocked or maintained (e.g., maintained in the locked state due to multiple recursive lock acquisitions). However, most well-formed applications that are written using a higher-level language (e.g., JAVA) and then compiled to bytecodes include matched pairs of lock acquisition and release operations (e.g., matched pairs of monitorenter and monitorexit JVM instructions) and, therefore, exhibit balanced synchronization characteristics (i.e., a locking sequence involving a balanced lock acquisition and release pair performed by the same thread). In code exhibiting balanced synchronization characteristics, the additional overhead of checking the lock owner and the lock recursion counter state variables may be unnecessary and, therefore, may reduce the overall efficiency of the executing application.